/*******************************************************************************
 * Copyright (c) 2000, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.text.template.contentassist;

import org.eclipse.che.ide.ext.java.shared.dto.LinkedModeModel;
import org.eclipse.che.jdt.JavadocFinder;
import org.eclipse.che.jdt.javadoc.HTMLPrinter;
import org.eclipse.che.jdt.javaeditor.HasLinkedModel;
import org.eclipse.che.jface.text.ITextViewer;
import org.eclipse.che.jface.text.contentassist.ICompletionProposal;
import org.eclipse.che.jface.text.contentassist.ICompletionProposalExtension2;
import org.eclipse.che.jface.text.contentassist.ICompletionProposalExtension3;
import org.eclipse.che.jface.text.contentassist.ICompletionProposalExtension4;
import org.eclipse.che.jface.text.contentassist.ICompletionProposalExtension6;
import org.eclipse.che.jface.text.contentassist.IContextInformation;
import org.eclipse.che.jface.text.link.ProposalPosition;
import org.eclipse.che.jface.text.source.LineRange;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.internal.corext.template.java.CompilationUnitContext;
import org.eclipse.jdt.internal.corext.template.java.JavaDocContext;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.IndentUtil;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.templates.DocumentTemplateContext;
import org.eclipse.jface.text.templates.GlobalTemplateVariables;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateBuffer;
import org.eclipse.jface.text.templates.TemplateContext;
import org.eclipse.jface.text.templates.TemplateException;
import org.eclipse.jface.text.templates.TemplateVariable;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;

import static org.eclipse.che.plugin.java.server.dto.DtoServerImpls.LinkedDataImpl;
import static org.eclipse.che.plugin.java.server.dto.DtoServerImpls.LinkedModeModelImpl;
import static org.eclipse.che.plugin.java.server.dto.DtoServerImpls.LinkedPositionGroupImpl;
import static org.eclipse.che.plugin.java.server.dto.DtoServerImpls.RegionImpl;

/**
 * A template proposal.
 */
public class TemplateProposal implements IJavaCompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension3,
										 ICompletionProposalExtension4, ICompletionProposalExtension6, HasLinkedModel {

	private final Template        fTemplate;
	private final TemplateContext fContext;
	private final Image           fImage;
	private final IRegion         fRegion;
	private       int             fRelevance;

	private IRegion                  fSelectedRegion; // initialized by apply()
	private StyledString             fDisplayString;
	private InclusivePositionUpdater fUpdater;
    private LinkedModeModel linkedModeModel;

    /**
     * Creates a template proposal with a template and its context.
     *
     * @param template  the template
     * @param context   the context in which the template was requested.
     * @param region    the region this proposal is applied to
     * @param image     the icon of the proposal.
     */
    public TemplateProposal(Template template, TemplateContext context, IRegion region, Image image) {
        Assert.isNotNull(template);
        Assert.isNotNull(context);
        Assert.isNotNull(region);

        fTemplate = template;
        fContext = context;
        fImage = image;
        fRegion = region;

        fDisplayString = null;

        fRelevance = computeRelevance();
    }

    /**
     * Computes the relevance to match the relevance values generated by the
     * core content assistant.
     *
     * @return a sensible relevance value.
     */
    private int computeRelevance() {
        // see org.eclipse.jdt.internal.codeassist.RelevanceConstants
        final int R_DEFAULT = 0;
        final int R_INTERESTING = 5;
        final int R_CASE = 10;
        final int R_NON_RESTRICTED = 3;
        final int R_EXACT_NAME = 4;
        final int R_INLINE_TAG = 31;

        int base = R_DEFAULT + R_INTERESTING + R_NON_RESTRICTED;

        try {
            if (fContext instanceof DocumentTemplateContext) {
                DocumentTemplateContext templateContext = (DocumentTemplateContext)fContext;
                IDocument document = templateContext.getDocument();

                String content = document.get(fRegion.getOffset(), fRegion.getLength());
                if (content.length() > 0 && fTemplate.getName().startsWith(content))
                    base += R_CASE;
                if (fTemplate.getName().equalsIgnoreCase(content))
                    base += R_EXACT_NAME;
                if (fContext instanceof JavaDocContext)
                    base += R_INLINE_TAG;
            }
        } catch (BadLocationException e) {
            // ignore - not a case sensitive match then
        }

        // see CompletionProposalCollector.computeRelevance
        // just under keywords, but better than packages
        final int TEMPLATE_RELEVANCE = 1;
        return base * 16 + TEMPLATE_RELEVANCE;
    }

    /**
     * Returns the template of this proposal.
     *
     * @return the template of this proposal
     * @since 3.1
     */
    public final Template getTemplate() {
        return fTemplate;
    }

    /**
     * Returns the context in which the template was requested.
     *
     * @return the context in which the template was requested
     * @since 3.1
     */
    protected final TemplateContext getContext() {
        return fContext;
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated This method is no longer called by the framework and clients should overwrite
     *             {@link #apply(ITextViewer, char, int, int)} instead
     */
    public final void apply(IDocument document) {
        // not called anymore
    }

    /*
     * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, char, int, int)
     */
    public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {

        IDocument document = viewer.getDocument();
        try {
            fContext.setReadOnly(false);
            int start;
            TemplateBuffer templateBuffer;
            try {
                beginCompoundChange(viewer);

                int oldReplaceOffset = getReplaceOffset();
                try {
                    // this may already modify the document (e.g. add imports)
                    templateBuffer = fContext.evaluate(fTemplate);
                } catch (TemplateException e1) {
                    fSelectedRegion = fRegion;
                    return;
                }

                start = getReplaceOffset();
                int shift = start - oldReplaceOffset;
                int end = Math.max(getReplaceEndOffset(), offset + shift);

                // insert template string
                if (end > document.getLength())
                    end = offset;
                String templateString = templateBuffer.getString();
                document.replace(start, end - start, templateString);
            } finally {
                endCompoundChange(viewer);
            }

            // translate positions
            LinkedModeModelImpl model = new LinkedModeModelImpl();
			TemplateVariable[] variables = templateBuffer.getVariables();

			MultiVariableGuess guess= fContext instanceof CompilationUnitContext ? ((CompilationUnitContext) fContext).getMultiVariableGuess() : null;

			boolean hasPositions= false;
			for (int i= 0; i != variables.length; i++) {
				TemplateVariable variable= variables[i];

				if (variable.isUnambiguous())
					continue;

				LinkedPositionGroupImpl group= new LinkedPositionGroupImpl();

				int[] offsets= variable.getOffsets();
				int length= variable.getLength();

				LinkedPosition first;
				if (guess != null && variable instanceof MultiVariable) {
					first= new VariablePosition(document, offsets[0] + start, length, guess, (MultiVariable) variable);
					guess.addSlave((VariablePosition) first);
				} else {
					String[] values= variable.getValues();
					ICompletionProposal[] proposals= new ICompletionProposal[values.length];
					for (int j= 0; j < values.length; j++) {
//						ensurePositionCategoryInstalled(document, model);
						Position pos= new Position(offsets[0] + start, length);
//						document.addPosition(getCategory(), pos);
						proposals[j]= new PositionBasedCompletionProposal(values[j], pos, length);
					}

					if (proposals.length > 1)
						first= new ProposalPosition(document, offsets[0] + start, length, proposals);
					else
						first= new LinkedPosition(document, offsets[0] + start, length);
				}

				for (int j= 0; j != offsets.length; j++)
					if (j == 0) {
                        if(first instanceof ProposalPosition){
                            RegionImpl region = new RegionImpl();
                            region.setLength(first.getLength());
                            region.setOffset(first.getOffset());
                            LinkedDataImpl data = new LinkedDataImpl();
                            ICompletionProposal[] choices = ((ProposalPosition)first).getChoices();
                            if(choices != null) {
                                for (ICompletionProposal choice : choices) {
                                    data.addValues(choice.getDisplayString());
                                }
                                group.setData(data);
                            }
                            group.addPositions(region);
                        } else {
                            RegionImpl region = new RegionImpl();
                            region.setLength(first.getLength());
                            region.setOffset(first.getOffset());
                            group.addPositions(region);
                        }
                    }
					else {
                        RegionImpl region = new RegionImpl();
                        region.setLength(length);
                        region.setOffset(offsets[j] + start);
                        group.addPositions(region);
                    }

				model.addGroups(group);
				hasPositions= true;
			}

			if (hasPositions) {
				model.setEscapePosition(getCaretOffset(templateBuffer) + start);
                this.linkedModeModel = model;
//				model.forceInstall();
//				JavaEditor editor= getJavaEditor();
//				if (editor != null) {
//					model.addLinkingListener(new EditorHighlightingSynchronizer(editor));
//				}
//
//				LinkedModeUI ui= new EditorLinkedModeUI(model, viewer);
//				ui.setExitPosition(viewer, getCaretOffset(templateBuffer) + start, 0, Integer.MAX_VALUE);
//				ui.enter();

				fSelectedRegion= fRegion;//ui.getSelectedRegion();
			} else {
				fSelectedRegion= new Region(getCaretOffset(templateBuffer) + start, 0);
			}

		} catch (BadLocationException e) {
			JavaPlugin.log(e);
//			openErrorDialog(viewer.getTextWidget().getShell(), e);
			fSelectedRegion= fRegion;
		}

    }

	private void endCompoundChange(ITextViewer viewer) {
//		if (viewer instanceof ITextViewerExtension) {
//			ITextViewerExtension extension= (ITextViewerExtension) viewer;
//			IRewriteTarget target= extension.getRewriteTarget();
//			target.endCompoundChange();
//		}
	}

	private void beginCompoundChange(ITextViewer viewer) {
//		if (viewer instanceof ITextViewerExtension) {
//			ITextViewerExtension extension= (ITextViewerExtension) viewer;
//			IRewriteTarget target= extension.getRewriteTarget();
//			target.beginCompoundChange();
//		}
	}

//	/**
//	 * Returns the currently active java editor, or <code>null</code> if it
//	 * cannot be determined.
//	 *
//	 * @return  the currently active java editor, or <code>null</code>
//	 */
//	private JavaEditor getJavaEditor() {
//		IEditorPart part= JavaPlugin.getActivePage().getActiveEditor();
//		if (part instanceof JavaEditor)
//			return (JavaEditor) part;
//		else
//			return null;
//	}

//	private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) {
//		if (!document.containsPositionCategory(getCategory())) {
//			document.addPositionCategory(getCategory());
//			fUpdater= new InclusivePositionUpdater(getCategory());
//			document.addPositionUpdater(fUpdater);
//
//			model.addLinkingListener(new ILinkedModeListener() {
//
//				/*
//				 * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, int)
//				 */
//				public void left(LinkedModeModel environment, int flags) {
//					ensurePositionCategoryRemoved(document);
//				}
//
//				public void suspend(LinkedModeModel environment) {}
//				public void resume(LinkedModeModel environment, int flags) {}
//			});
//		}
//	}

	private void ensurePositionCategoryRemoved(IDocument document) {
		if (document.containsPositionCategory(getCategory())) {
			try {
				document.removePositionCategory(getCategory());
			} catch (BadPositionCategoryException e) {
				// ignore
			}
			document.removePositionUpdater(fUpdater);
		}
	}

	private String getCategory() {
		return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$
	}

	private int getCaretOffset(TemplateBuffer buffer) {

	    TemplateVariable[] variables= buffer.getVariables();
		for (int i= 0; i != variables.length; i++) {
			TemplateVariable variable= variables[i];
			if (variable.getType().equals(GlobalTemplateVariables.Cursor.NAME))
				return variable.getOffsets()[0];
		}

		return buffer.getString().length();
	}

	/**
	 * Returns the offset of the range in the document that will be replaced by
	 * applying this template.
	 *
	 * @return the offset of the range in the document that will be replaced by
	 *         applying this template
	 */
	protected final int getReplaceOffset() {
		int start;
		if (fContext instanceof DocumentTemplateContext) {
			DocumentTemplateContext docContext = (DocumentTemplateContext)fContext;
			start= docContext.getStart();
		} else {
			start= fRegion.getOffset();
		}
		return start;
	}

	/**
	 * Returns the end offset of the range in the document that will be replaced
	 * by applying this template.
	 *
	 * @return the end offset of the range in the document that will be replaced
	 *         by applying this template
	 */
	protected final int getReplaceEndOffset() {
		int end;
		if (fContext instanceof DocumentTemplateContext) {
			DocumentTemplateContext docContext = (DocumentTemplateContext)fContext;
			end= docContext.getEnd();
		} else {
			end= fRegion.getOffset() + fRegion.getLength();
		}
		return end;
	}

	/*
	 * @see ICompletionProposal#getSelection(IDocument)
	 */
	public Point getSelection(IDocument document) {
		return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength());
	}

	/*
	 * @see ICompletionProposal#getAdditionalProposalInfo()
	 */
	public String getAdditionalProposalInfo() {
	    try {
		    fContext.setReadOnly(true);
			TemplateBuffer templateBuffer;
			try {
				templateBuffer= fContext.evaluate(fTemplate);
			} catch (TemplateException e) {
				return null;
			}

			IDocument document= new Document(templateBuffer.getString());
			IndentUtil.indentLines(document, new LineRange(0, document.getNumberOfLines()), null, null);
			StringBuffer buffer = new StringBuffer();
			HTMLPrinter.insertPageProlog(buffer, 0, JavadocFinder.getStyleSheet());
			HTMLPrinter.addParagraph(buffer, document.get());
			HTMLPrinter.addPageEpilog(buffer);
			return buffer.toString();

	    } catch (BadLocationException e) {
//			handleException(
//					JavaPlugin.getActiveWorkbenchShell(), new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "", e))); //$NON-NLS-1$
			JavaPlugin.log(e);
			return null;
		}
	}

	/*
	 * @see ICompletionProposal#getDisplayString()
	 */
	public String getDisplayString() {
		return getStyledDisplayString().getString();
	}

	/*
	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension6#getStyledDisplayString()
	 * @since 3.4
	 */
	public StyledString getStyledDisplayString() {
		if (fDisplayString == null) {
			String[] arguments= new String[] { fTemplate.getName(), fTemplate.getDescription() };
			String decorated= Messages.format(TemplateContentAssistMessages.TemplateProposal_displayString, arguments);
			StyledString string= new StyledString(fTemplate.getName(), StyledString.COUNTER_STYLER);
			fDisplayString= StyledCellLabelProvider.styleDecoratedString(decorated, StyledString.QUALIFIER_STYLER, string);
		}
		return fDisplayString;
	}

	public void setDisplayString(StyledString displayString) {
		fDisplayString= displayString;
	}

	/*
	 * @see ICompletionProposal#getImage()
	 */
	public Image getImage() {
		return fImage;
	}

	/*
	 * @see ICompletionProposal#getContextInformation()
	 */
	public IContextInformation getContextInformation() {
		return null;
	}

//	private void openErrorDialog(Shell shell, Exception e) {
//		MessageDialog.openError(shell, TemplateContentAssistMessages.TemplateEvaluator_error_title, e.getMessage());
//	}

//	private void handleException(Shell shell, CoreException e) {
//		ExceptionHandler.handle(e, shell, TemplateContentAssistMessages.TemplateEvaluator_error_title, null);
//	}

	/*
	 * @see IJavaCompletionProposal#getRelevance()
	 */
	public int getRelevance() {
		return fRelevance;
	}

	public void setRelevance(int relevance) {
		fRelevance= relevance;
	}

//	/*
//	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
//	 */
//	public IInformationControlCreator getInformationControlCreator() {
//		int orientation;
//		IEditorPart editor= getJavaEditor();
//		if (editor instanceof IWorkbenchPartOrientation)
//			orientation= ((IWorkbenchPartOrientation)editor).getOrientation();
//		else
//			orientation= SWT.LEFT_TO_RIGHT;
//		return new TemplateInformationControlCreator(orientation);
//	}

	/*
	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean)
	 */
	public void selected(ITextViewer viewer, boolean smartToggle) {
	}

	/*
	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
	 */
	public void unselected(ITextViewer viewer) {
	}

	/*
	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent)
	 */
	public boolean validate(IDocument document, int offset, DocumentEvent event) {
		try {
			int replaceOffset= getReplaceOffset();
			if (offset >= replaceOffset) {
				String content= document.get(replaceOffset, offset - replaceOffset);
				String templateName= fTemplate.getName().toLowerCase();
				boolean valid= templateName.startsWith(content.toLowerCase());
				if (!valid && fContext instanceof JavaDocContext && templateName.startsWith("<")) { //$NON-NLS-1$
					valid= templateName.startsWith(content.toLowerCase(), 1);
				}
				return valid;
			}
		} catch (BadLocationException e) {
			// concurrent modification - ignore
		}
		return false;
	}

	/*
	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementString()
	 */
	public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
		// bug 114360 - don't make selection templates prefix-completable
		if (isSelectionTemplate())
			return ""; //$NON-NLS-1$
		return fTemplate.getName();
	}

	/*
	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementOffset()
	 */
	public int getPrefixCompletionStart(IDocument document, int completionOffset) {
		return getReplaceOffset();
	}

	/*
	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension4#isAutoInsertable()
	 */
	public boolean isAutoInsertable() {
		if (isSelectionTemplate())
			return false;
		return fTemplate.isAutoInsertable();
	}

	/**
	 * Returns <code>true</code> if the proposal has a selection, e.g. will wrap some code.
	 *
	 * @return <code>true</code> if the proposals completion length is non zero
	 * @since 3.2
	 */
	private boolean isSelectionTemplate() {
		if (fContext instanceof DocumentTemplateContext) {
			DocumentTemplateContext ctx= (DocumentTemplateContext) fContext;
			if (ctx.getCompletionLength() > 0)
				return true;
		}
		return false;
	}

	@Override
	public String toString() {
		return "TemplateProposal{" +
			   "fDisplayString=" + getDisplayString() +
			   '}';
	}

    @Override
    public LinkedModeModel getLinkedModel() {
        return linkedModeModel;
    }
}
